`timescale 1ns/1ps
`default_nettype none

/* NOTE:
* - 本模负责生成模组缝隙调整数据
* - ram中每个模组需要8x16bit存放调整系数
* - 每个模组调整系数的格式
*   16bit: 上边沿调整系数初始值
*   16bit: 上边沿调整系数递增值
*   16bit: 下边沿调整系数初始值
*   16bit: 下边沿调整系数递增值
*   16bit: 左边沿调整系数初始值
*   16bit: 左边沿调整系数递增值
*   16bit: 右边沿调整系数初始值
*   16bit: 右边沿调整系数递增值
* - 按照从左到右，从上到下的顺序依次存放各模组调整系数
*/

module gap_coe_reader (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_rst_n,
    // config
    input  wire [7:0]   I_cfg_module_width,  // 模组宽度
    input  wire [7:0]   I_cfg_module_height, // 模组高度
    input  wire [5:0]   I_cfg_module_col,    // 模组列数
    input  wire [15:0]  I_cfg_gap_center,    // 模组中间调整系数
    input  wire [10:0]  I_cfg_win_col_num,   // 带载列数（宽度）
    input  wire [10:0]  I_cfg_sector_width,  // 每个区域宽度
    input  wire [1:0]   I_cfg_box_dir,       // 箱体方向
    input  wire [1:0]   I_cfg_col_loop,      // 列模式读取循环次数(数据组数/8,向上取整)
    input  wire [7:0]   I_cfg_col_step,      // 列模式每次读地址间隔
    // gap ram
    output wire         O_gap_ram_rden,
    output wire [8:0]   O_gap_ram_addr,
    input  wire [15:0]  I_gap_ram_q,
    // read request
    input  wire         I_gap_coe_req,
    input  wire [9:0]   I_gap_coe_row,
    output wire         O_gap_coe_busy,
    // gap coe
    input  wire         I_gap_coe_ack,
    output wire [15:0]  O_gap_coe_val
);
//------------------------Parameter----------------------
// fsm
localparam [3:0]
    IDLE = 0,
    DIV  = 1,
    PREP = 2,
    RD0  = 3,
    RD1  = 4,
    RD2  = 5,
    RD3  = 6,
    NOP  = 7,
    WR0  = 8,
    WR1  = 9,
    WR2  = 10,
    LOOP = 11,
    DONE = 12;

// box direction
localparam [1:0]
    LANDSCAPE = 0, // 横向
    PORTRAIT0 = 1, // 纵向，第一个端口在左侧
    PORTRAIT1 = 2; // 纵向，第一个端口在右侧

//------------------------Local signal-------------------
// fsm
reg  [3:0]  state;
reg  [3:0]  next;
reg         top_flag;
reg         bottom_flag;
reg         border_over;
reg  [7:0]  wr_cnt;
reg  [5:0]  col_num;

// divider
wire        div_start;
wire [9:0]  div_numer;
wire [7:0]  div_denom;
wire        div_done;
wire [9:0]  div_quotient;
wire [7:0]  div_remain;

// ram
wire        ram_wren;
reg  [8:0]  ram_waddr;
reg  [15:0] ram_data;
wire        ram_rden;
reg  [8:0]  ram_raddr;
wire [15:0] ram_q;
wire [4:0]  cfg_port_max;
wire [8:0]  cfg_last_col;
reg  [4:0]  port_id;
reg  [8:0]  next_scan_addr;

// gap ram
reg  [8:0]  gap_addr;
reg  [15:0] param0;
reg  [15:0] param1;
reg  [15:0] param2;
reg  [15:0] param3;
reg         param0_valid;
reg         param1_valid;
reg         param2_valid;
reg         param3_valid;
reg  [3:0]  mult_flag;
reg  [15:0] mult_a;
reg  [7:0]  mult_b;
reg  [23:0] mult_r;
reg  [15:0] gap_left;
reg  [15:0] gap_right;
reg  [15:0] coe_buf;

// misc
wire [10:0] back_max; // 每隔多少像素需要进行一次回退(n+1)
wire [2:0]  back_num; // 回退数量
reg  [10:0] pixel_cnt;
reg         back_flag;

//------------------------Instantiation------------------
// serial_divider
serial_divider #(/*{{{*/
    .M          ( 10 ),
    .N          ( 8 ),
    .MODE       ( 0 )
) divider (
    .I_sclk     ( I_sclk ),
    .I_rst_n    ( I_rst_n ),
    .I_start    ( div_start ),
    .I_numer    ( div_numer ),
    .I_denom    ( div_denom ),
    .O_done     ( div_done ),
    .O_quotient ( div_quotient ),
    .O_remain   ( div_remain )
);/*}}}*/

// sdpram_512x16
sdpram_512x16 ram (/*{{{*/
    .clock     ( I_sclk ),
    .data      ( ram_data ),
    .rdaddress ( ram_raddr ),
    .rden      ( ram_rden ),
    .wraddress ( ram_waddr ),
    .wren      ( ram_wren ),
    .q         ( ram_q )
);/*}}}*/


// ad_mem #(

  // .DATA_WIDTH   (   16  ),
  // .ADDRESS_WIDTH(   9   )
  // ) 
// ram(
    // .clka       (   I_sclk      ),
    // .wea        (   ram_wren    ),
    // .addra      (   ram_waddr   ),
    // .dina       (   ram_data    ),

    // .clkb       (   I_sclk      ),
    // .reb        (   ram_rden    ),
    // .addrb      (   ram_raddr   ),
    // .doutb      (   ram_q       )
    
// );

//------------------------Body---------------------------
//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
// state
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            if (I_gap_coe_req)
                next = DIV;
            else
                next = IDLE;
        end

        DIV: begin
            if (!div_done)
                next = DIV;
            else
                next = PREP;
        end

        PREP: begin
            next = RD0;
        end

        RD0: begin
            next = RD1;
        end

        RD1: begin
            if (top_flag || bottom_flag)
                next = NOP;
            else
                next = RD2;
        end

        RD2: begin
            next = RD3;
        end

        RD3: begin
            next = NOP;
        end

        NOP: begin
            next = WR0;
        end

        WR0: begin
            if (!border_over)
                next = WR0;
            else if (top_flag || bottom_flag)
                next = LOOP;
            else
                next = WR1;
        end

        WR1: begin
            next = WR2;
        end

        WR2: begin
            next = LOOP;
        end

        LOOP: begin
            if (col_num == I_cfg_module_col)
                next = DONE;
            else
                next = RD0;
        end

        DONE: begin
            next = IDLE;
        end

        default: begin
            next = IDLE;
        end
    endcase
end

// top_flag
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        top_flag <= 1'b0;
    else if (div_done) begin
        if (div_remain == 1'b0)
            top_flag <= 1'b1;
        else
            top_flag <= 1'b0;
    end
end

// bottom_flag
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        bottom_flag <= 1'b0;
    else if (div_done) begin
        if (div_remain == I_cfg_module_height - 1'b1)
            bottom_flag <= 1'b1;
        else
            bottom_flag <= 1'b0;
    end
end

// border_over
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        border_over <= 1'b0;
    else if (state == RD0)
        border_over <= 1'b0;
    else if (state == WR0 && wr_cnt == 1'b1)
        border_over <= 1'b1;
end

// wr_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        wr_cnt <= 1'b0;
    else if (state == RD0) begin
        if (top_flag || bottom_flag)
            wr_cnt <= I_cfg_module_width - 1'b1;
        else
            wr_cnt <= I_cfg_module_width - 2'd3;
    end
    else if (state == WR0)
        wr_cnt <= wr_cnt - 1'b1;
end

// col_num
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        col_num <= 1'b1;
    else if (state == PREP)
        col_num <= 1'b1;
    else if (state == LOOP)
        col_num <= col_num + 1'b1;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++divider++++++++++++++++++++++++
assign div_start = (state == IDLE) && I_gap_coe_req;
assign div_numer = I_gap_coe_row;
assign div_denom = I_cfg_module_height;
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++read request+++++++++++++++++++
assign O_gap_coe_busy = (state != IDLE);
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++write ram++++++++++++++++++++++
assign ram_wren = (state == WR0) | (state == WR1) | (state == WR2);

// ram_waddr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        ram_waddr <= 1'b0;
    else if (state == PREP) begin
        if (top_flag || bottom_flag)
            ram_waddr <= 1'b0;
        else
            ram_waddr <= 1'b1;
    end
    else if (state == WR0)
        ram_waddr <= ram_waddr + 1'b1;
    else if (state == WR1)
        ram_waddr <= ram_waddr - (I_cfg_module_width - 1'b1);
    else if (state == WR2)
        ram_waddr <= ram_waddr + (I_cfg_module_width + 1'b1);
end

// ram_data
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        ram_data <= 1'b0;
    else if (state == NOP) begin
        if (top_flag || bottom_flag)
            ram_data <= param0;
        else
            ram_data <= I_cfg_gap_center;
    end
    else if (state == WR0) begin
        if (top_flag || bottom_flag)
            ram_data <= ram_data + param1; // 上/下边沿，递增
        else if (border_over)
            ram_data <= gap_right; // 右边沿
    end
    else if (state == WR1)
        ram_data <= gap_left; // 左边沿
end

// coe_buf
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        coe_buf <= 16'hffff;
    else if (ram_rden)
        coe_buf <= ram_q;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++read ram+++++++++++++++++++++++
assign ram_rden = (state == LOOP && next == DONE) 
                || (state == DONE) || I_gap_coe_ack;

assign cfg_port_max = {I_cfg_col_loop, 3'd0} - 1'b1;
assign cfg_last_col = I_cfg_win_col_num[8:0] - 1'b1;

// port_id
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        port_id <= 1'b0;
    else if (!O_gap_coe_busy && I_gap_coe_req)
        port_id <= 1'b0;
    else if (ram_rden) begin
        if (port_id == cfg_port_max)
            port_id <= 1'b0;
        else
            port_id <= port_id + 1'b1;
    end
end

// next_scan_addr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        next_scan_addr <= 1'b0;
    else if (!O_gap_coe_busy && I_gap_coe_req) begin
        if (I_cfg_box_dir == PORTRAIT1)
            next_scan_addr <= cfg_last_col;
        else
            next_scan_addr <= 1'b0;
    end
    else if (ram_rden && port_id == 1'b0) begin
        if (I_cfg_box_dir == PORTRAIT1)
            next_scan_addr <= next_scan_addr - 1'b1;
        else
            next_scan_addr <= next_scan_addr + 1'b1;
    end
end

// ram_raddr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        ram_raddr <= 1'b0;
    else if (!O_gap_coe_busy && I_gap_coe_req) begin
        if (I_cfg_box_dir == PORTRAIT1)
            // NOTE: 列模式下，如果第一个端口在右边，则反着读取数据
            ram_raddr <= cfg_last_col;
        else
            ram_raddr <= 1'b0;
    end
    else if (ram_rden) begin
        if (I_cfg_box_dir == PORTRAIT0) begin // 列模式，顺序
            if (port_id == cfg_port_max)
                ram_raddr <= next_scan_addr;
            else
                ram_raddr <= ram_raddr + I_cfg_col_step;
        end
        else if (I_cfg_box_dir == PORTRAIT1) begin // 列模式，逆序
            if (port_id == cfg_port_max)
                ram_raddr <= next_scan_addr;
            else
                ram_raddr <= ram_raddr - I_cfg_col_step;
        end
        else begin // 行模式
            if (back_flag)
                ram_raddr <= ram_raddr + 1'b1 - back_num;
            else
                ram_raddr <= ram_raddr + 1'b1;
        end
    end
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++gap ram++++++++++++++++++++++++
assign O_gap_ram_rden = (state == RD0) | (state == RD1)
                      | (state == RD2) | (state == RD3);
assign O_gap_ram_addr = gap_addr;

// param?_valid
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n) begin
        param0_valid <= 1'b0;
        param1_valid <= 1'b0;
        param2_valid <= 1'b0;
        param3_valid <= 1'b0;
    end
    else begin
        param0_valid <= (state == RD0);
        param1_valid <= (state == RD1);
        param2_valid <= (state == RD2);
        param3_valid <= (state == RD3);
    end
end

// param?
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n) begin
        param0 <= 1'b0;
        param1 <= 1'b0;
        param2 <= 1'b0;
        param3 <= 1'b0;
    end
    else begin
        if (param0_valid) param0 <= I_gap_ram_q;
        if (param1_valid) param1 <= I_gap_ram_q;
        if (param2_valid) param2 <= I_gap_ram_q;
        if (param3_valid) param3 <= I_gap_ram_q;
    end
end

// gap_addr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        gap_addr <= 1'b0;
    else if (div_done)
        gap_addr <= {I_cfg_module_col, 3'd0} * div_quotient[5:0];
    else if (state == WR0 && border_over && (top_flag || bottom_flag))
        gap_addr <= {gap_addr[8:3], 3'd0} + 4'd8;
    else if (state == PREP || state == LOOP) begin
        if (bottom_flag)
            gap_addr[2:0] <= 3'd2;
        else if (!top_flag)
            gap_addr[2:0] <= 3'd4;
    end
    else if (O_gap_ram_rden)
        gap_addr <= gap_addr + 1'b1;
end

// mult_flag
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        mult_flag <= 1'b0;
    else if (state == RD3)
        mult_flag <= 1'b1;
    else
        mult_flag <= mult_flag << 1'b1;
end

// mult_a
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        mult_a <= 1'b0;
    else if (mult_flag[0])
        mult_a <= param1;
    else if (mult_flag[1])
        mult_a <= param3;
end

// mult_b
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        mult_b <= 1'b0;
    else if (div_done)
        mult_b <= div_remain - 1'b1;
end

// mult_r
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        mult_r <= 1'b0;
    else
        mult_r <= $signed(mult_a) * $signed(mult_b);
end

// gap_left
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        gap_left <= 1'b0;
    else if (mult_flag[2])
        gap_left <= mult_r[15:0] + param0;
end

// gap_right
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        gap_right <= 1'b0;
    else if (mult_flag[3])
        gap_right <= mult_r[15:0] + param2;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++misc+++++++++++++++++++++++++++
assign O_gap_coe_val = coe_buf;

assign back_max = (I_cfg_sector_width - 1'b1) | 3'd7;
assign back_num = 3'd0 - I_cfg_sector_width[2:0];

// pixel_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        pixel_cnt <= 1'b0;
    else if (!O_gap_coe_busy && I_gap_coe_req)
        pixel_cnt <= back_max;
    else if (ram_rden) begin
        if (back_flag)
            pixel_cnt <= back_max;
        else
            pixel_cnt <= pixel_cnt - 1'b1;
    end
end

// back_flag
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        back_flag <= 1'b0;
    else if (!O_gap_coe_busy && I_gap_coe_req)
        back_flag <= 1'b0;
    else if (ram_rden) begin
        if (pixel_cnt == 1'b1)
            back_flag <= 1'b1;
        else
            back_flag <= 1'b0;
    end
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
